﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;

namespace Red.Controls.ViewModels
{
    #region DataItemViewModel

    public class DataItemViewModel
    {
        #region Data

        private Dictionary<string, DataItemPropertyViewModel> _properties = new Dictionary<string, DataItemPropertyViewModel>();
        private DataItem _dataItem = null;

        #endregion

        #region Public Methods

        public async Task InitializeAsync( DataItem item )
        {
            _dataItem = item;
            if ( _dataItem != null )
            {
                await _dataItem.RebuildAsync();
            }
        }

        public async void InitializeProperties()
        {
            List<Task> tasks = new List<Task>();
            List<string> keys = _properties.Keys.ToList();

            foreach ( string item in keys )
            {
                DataItemPropertyViewModel property = _properties[item];
                tasks.Add( property.InitializeAsync( _dataItem ) );
            }

            await Task.WhenAll( tasks.ToArray() );
        }

        public void Deinitialize()
        {
            // Remove registred properties callback
            DeinitializeProperties();
        }

        #endregion

        #region Public properties Getters/Setters

        public void RegisterProperty( string propertyName, string propertyInteropDataPath, Action<string, string> onPropertyChange )
        {
            _properties.Add( propertyName, new DataItemPropertyViewModel( propertyName, propertyInteropDataPath, onPropertyChange ) );
        }

        public string GetValue( string propertyName )
        {
            if ( _dataItem != null )
            {
                DataItemPropertyViewModel property;
                if ( _properties.TryGetValue( propertyName, out property ) )
                {
                    return property.Value;
                }
            }

            return String.Empty;
        }

        public T GetValue<T>( string propertyName, T def = default( T ) ) where T : IConvertible
        {
            string value = GetValue( propertyName );
            if ( !String.IsNullOrEmpty( value ) )
            {
                var converter = TypeDescriptor.GetConverter( typeof( T ) );
                if ( converter.IsValid( value ) )
                    return (T)converter.ConvertFrom( value );
                else
                    return def;
            }
            else
                return def;
        }
      
        public bool SetValue( string propertyName, string value )
        {
            if ( _dataItem != null )
            {
                DataItemPropertyViewModel property;
                if ( _properties.TryGetValue( propertyName, out property ) )
                {
                    if ( property.Value != value )
                    {
                        property.Value = value;
                        return true;
                    }
                }
            }
            return false;
        }

        public bool SetValue<T>( string propertyName, double value ) where T : IConvertible
        {
            return SetValue( propertyName, value.ToString() );
        }

        #endregion

        #region Private Methods

        private void DeinitializeProperties()
        {
            var keys = _properties.Keys.ToList();
            foreach ( string item in keys )
            {
                DataItemPropertyViewModel property = _properties[item];
                property.DeinitializeProperty();
            }
        }

        #endregion
    }

    #endregion

    #region class DataItemPropertyViewModel

    internal class DataItemPropertyViewModel
    {
        #region Data

        private string _propertyInteropDataPath = String.Empty;
        private string _propertyName = String.Empty;

        private Action<string, string> _changeAction = null;
        private DataItem _dataItem = null;

        #endregion

        #region C-tor

        public DataItemPropertyViewModel( string propertyName, string interopDataPath, Action<string, string> changeAction )
        {
            _propertyInteropDataPath = interopDataPath;
            _propertyName = propertyName;
            _changeAction = changeAction;
        }

        #endregion

        #region Properties

        public string Value
        {
            get
            {
                if ( _dataItem != null )
                {
                    return _dataItem.Value;
                }

                return String.Empty;
            }
            set
            {
                if ( _dataItem == null || _dataItem.Value == value )
                {
                    return;
                }

                _dataItem.Value = value;

                if ( _changeAction != null )
                {
                    // We need to notify UI now! We cannot wait for async callback. This means that 
                    // update callback will be fired two times :(
                    _changeAction( _propertyName, _dataItem.Value );
                }
            }
        }

        #endregion

        #region Public Methods

        public async Task InitializeAsync( DataItem parentDataItem )
        {
            DataItem item = await parentDataItem.DigUpSubItemAsync( _propertyInteropDataPath );
            if ( !item.IsValid )
            {
                throw new ApplicationException( "DataItem is invalid" );
            }

            if ( item != _dataItem )
            {
                _dataItem = item;

                //await _dataItem.RebuildAsync();
                DeinitializeProperty();

                if ( _dataItem != null )
                {
                    _dataItem.PropertyChanged += DataItem_PropertyChanged;
                    if ( _changeAction != null )
                    {
                        _changeAction( _propertyName, _dataItem.Value );
                    }
                }
            }
        }

        public void DeinitializeProperty()
        {
            if ( _dataItem != null )
            {
                _dataItem.PropertyChanged -= DataItem_PropertyChanged;
            }            
        }

        #endregion

        #region Private Methods

        private void DataItem_PropertyChanged( object sender, PropertyChangedEventArgs e )
        {
            if ( e.PropertyName == "Value" && _changeAction != null )
            {
                _changeAction( _propertyName, Value );
            }
        }

        #endregion
    }

    #endregion
}
